﻿using System;
using System.Collections.Generic;
using System.Linq;
using System.Text;
using System.Web;
using System.Web.Security;
using System.Web.Configuration;
using System.Security.Permissions;
using LibYanu.Security.DTO;
using LibYanu.Security.Model;
using LibYanu.Security;
using NHibernate.Criterion;
using System.Security.Cryptography;
namespace LibYanu.Web.Provider
{
    [AspNetHostingPermission(SecurityAction.LinkDemand, Level = AspNetHostingPermissionLevel.Minimal)]
    [AspNetHostingPermission(SecurityAction.InheritanceDemand, Level = AspNetHostingPermissionLevel.Minimal)]
    public class YanuMembershipProvider : MembershipProvider
    {
        
        
        

        #region Fields

        private bool requiresQuestionAndAnswer;
        private bool _requiresUniqueEmail;
        private bool enablePasswordRetrieval;
        private bool enablePasswordReset;
        private int maxInvalidPasswordAttempts;
        private int passwordAttemptWindow;
        private MembershipPasswordFormat passwordFormat = MembershipPasswordFormat.Clear;//  MembershipPasswordFormat.Hashed;
        private int _minRequiredPasswordLength;
        private int minRequiredNonAlphanumericCharacters;
        private string passwordStrengthRegularExpression;
        private string _ApplicationName;
        private MachineKeySection machineKey = new MachineKeySection();

        #endregion Fields
        public override void Initialize(string name, System.Collections.Specialized.NameValueCollection config)
        {
            base.Initialize(name, config);
            //  _minRequiredPasswordLength = Convert.ToInt16(config["minRequiredPasswordLength"]);
            _ApplicationName = WebConfigurationManager.AppSettings["ApplicationName"];
            _requiresUniqueEmail = true;


            

        }

        public override int MinRequiredPasswordLength
        {
            get { return Membership.Provider.MinRequiredPasswordLength; }
        }

        public override string ApplicationName
        {
            get { return _ApplicationName; }
            set { _ApplicationName = value; }

        }

        public override bool ChangePassword(string username, string oldPassword, string newPassword)
        {
            using(var sc= new SecurityScope())
            {
                ValidatePasswordEventArgs args = new ValidatePasswordEventArgs(username, newPassword, true);
                OnValidatingPassword(args);
                if (args.Cancel)
                {
                    // Check for a specific error message.
                    if (null != args.FailureInformation)
                    {
                        throw (args.FailureInformation);
                    }
                    else
                    {
                        
                    }
                }

                var res = sc.Session.CreateCriteria(typeof(AspnetMembership), "us")
                        .Add(Expression.Eq("us.UserName", username))
                        .UniqueResult<AspnetMembership>();
                if (res != null)
                {
                    try
                    {
                        using(var tx= sc.Session.BeginTransaction())
                        {
                            res.Password = EncodePassword(newPassword, res.PasswordSalt, PasswordFormat);
                            res.LastPasswordChangedDate = DateTime.Now;
                            res.LastActivityDate = DateTime.Now;
                            sc.Session.Update(res);
                            tx.Commit();
                            return true;
                        }
                        
                    }
                    catch (Exception ex)
                    {
                        throw ex;
                    }
                }  
            }
            return false;
        }

        public override bool ChangePasswordQuestionAndAnswer(string username, string password, string newPasswordQuestion, string newPasswordAnswer)
        {
            throw new NotImplementedException();
        }

        public override MembershipUser CreateUser(string username, string password, string email, string passwordQuestion, string passwordAnswer, bool isApproved, object providerUserKey, out MembershipCreateStatus status)
        {
            machineKey.DecryptionKey = CreateKey(24);
            machineKey.ValidationKey = CreateKey(64);
             using (var s = new SecurityScope())
            {
                using (var tx = s.Session.BeginTransaction())
                {
                    try
                    {
                        
                        ValidatePasswordEventArgs arg = new ValidatePasswordEventArgs(username, password, true);
                        OnValidatingPassword(arg);
                        if (arg.Cancel)
                        {
                            status = MembershipCreateStatus.InvalidPassword;
                            return null;
                        }

                        if (RequiresUniqueEmail && !string.IsNullOrEmpty(GetUserNameByEmail(email)))
                        {
                           
                            status = MembershipCreateStatus.DuplicateEmail;
                            return null;
                        }

                        //Attempt to get the user record associated to the given user name.
                         if (s.Session.CreateCriteria(typeof(AspnetUser), "user")
                            .Add(Expression.Eq("user.UserName", username))
                            .SetProjection(Projections.RowCount())
                            .UniqueResult<int>() > 0)
                        {
                            // Indicate we have found an existing user record.
                            status = MembershipCreateStatus.DuplicateUserName;
                            return null;
                        }
                        else
                        {
                            // NOTE: providerUserKey is ignored on purpose. In this implementation it represents the user identifier.

                            AspnetMembership user = s.Session.CreateCriteria(typeof(AspnetMembership))
                            .Add(Expression.Eq("UserName", username))
                            .SetMaxResults(1)
                            .UniqueResult<AspnetMembership>();
                            if (user == null)
                            {
                                user = new AspnetMembership();
                                user.UserName = username;
                                user.LoweredUserName = username.ToLower();
                                user.AspnetApplication = new AplicationOp().getApplicationDtoByName(_ApplicationName,true).ToEntity();
                                user.LastActivityDate = DateTime.Now;
                                user.LoweredUserName = username.ToLower();
                                user.Comment = string.Format("Created on {0}", DateTime.Now);
                                user.Password = EncodePassword(password,machineKey.ValidationKey,passwordFormat);
                                user.PasswordFormat = (int)passwordFormat;
                                user.PasswordSalt = machineKey.ValidationKey;
                                user.IsApproved = isApproved;
                                user.IsLockedOut=false;
                                user.Email = email;
                                user.CreateDate = DateTime.Now;
                                user.LastActivityDate = DateTime.Now;
                                user.LastLoginDate = DateTime.Now;
                                user.LastLockoutDate = DateTime.Now;
                                user.LastPasswordChangedDate = DateTime.Now;
                                user.FailedPasswordAnswerAttemptCount = user.FailedPasswordAttemptCount = 0;
                                user.FailedPasswordAnswerAttemptWindowStart = user.FailedPasswordAttemptWindowStart = DateTime.Now;

                            }

                            //user.Applications.Add(session.Load<Application>(this.application.ID));
                            s.Session.SaveOrUpdate(user);
                            tx.Commit();

                            status = MembershipCreateStatus.Success;



                         }

                        
                    }
                    catch (Exception ex)
                    {

                        throw ex;
                    }
                }
                
                //var lu = GetUser(mdl.UserName, false);


            }


             // Return the newly created user record.
             var rslt = GetUser(username, false);

             return rslt;
            //var k = Membership.Provider.CreateUser(username, password, email, null, null, true, null, out status);

            //using (var sc
            //return new MembershipUser("yanu", "sdfsd", 2423, "sfasd", "safsf", "asfasdf", false, false, DateTime.Now, DateTime.Now, DateTime.Now, DateTime.Now, DateTime.Now);

        }
        

        public override bool DeleteUser(string username, bool deleteAllRelatedData)
        {
            bool ret = false;
            using(var sc= new SecurityScope())
            using (var tx = sc.Session.BeginTransaction())
            {
                try
                {
                    if (deleteAllRelatedData)
                    {                      
                        //sc.Session.CreateQuery("delete from AspnetRole u where u.Id.UserId = (select b.Id from AspnetUsers b where b.Name = :uname)")
                        //  .SetParameter("uname", username)
                        //  .ExecuteUpdate();
                    }
                    ret = sc.Session.CreateQuery("delete  AspnetUser  where UserName = :uname")
                                .SetParameter("uname", username)
                                .ExecuteUpdate() > 0;
                    tx.Commit();                   

                }
                catch (Exception ex)
                {
                    tx.Rollback();
                    throw ex;
                }                
            }
            return ret;
        }

        public override bool EnablePasswordReset
        {
            get { return Membership.Provider.EnablePasswordReset; }
        }

        public override bool EnablePasswordRetrieval
        {
            get { return Membership.Provider.EnablePasswordRetrieval; }
        }

        public override MembershipUserCollection FindUsersByEmail(string emailToMatch, int pageIndex, int pageSize, out int totalRecords)
        {
            throw new NotImplementedException();
        }

        public override MembershipUserCollection FindUsersByName(string usernameToMatch, int pageIndex, int pageSize, out int totalRecords)
        {
            throw new NotImplementedException();
        }

        public override MembershipUserCollection GetAllUsers(int pageIndex, int pageSize, out int totalRecords)
        {
            throw new NotImplementedException();
        }

        public override int GetNumberOfUsersOnline()
        {
            throw new NotImplementedException();
        }

        public override string GetPassword(string username, string answer)
        {
            throw new NotImplementedException();
        }

        public override MembershipUser GetUser(string username, bool userIsOnline)
        {
            MembershipUser res = null;
            using (var sc = new SecurityScope())
            {
              var usr = sc.Session.CreateCriteria(typeof(VwAspnetMembershipUser), "mu")
                        .Add(Expression.Eq("mu.UserName", username))
                        .SetMaxResults(1)
                        .UniqueResult<VwAspnetMembershipUser>();

                if(usr!=null)
                {
                    try
                    {
                        res = new MembershipUser(Membership.Provider.Name, usr.UserName, usr.UserId, usr.Email, usr.PasswordQuestion, usr.Comment, usr.IsApproved, usr.IsLockedOut, usr.CreateDate,
                                            usr.LastLoginDate, usr.LastActivityDate, usr.LastPasswordChangedDate, usr.LastLockoutDate);
                    }
                    catch (Exception ex)
                    {

                        throw ex;
                    }
                    
                }               

            }
            return res;
        }

        public override MembershipUser GetUser(object providerUserKey, bool userIsOnline)
        {
            throw new NotImplementedException();
        }

        public override string GetUserNameByEmail(string email)
        {
            string res = null;
            
            using (var sc = new SecurityScope())
            {
                var usr = sc.Session.CreateCriteria(typeof(VwAspnetMembershipUser), "mu")
                          .Add(Expression.Eq("mu.Email", email))                          
                          .SetMaxResults(1)
                          .SetProjection(Projections.Property("mu.Email"))
                          .UniqueResult<string>();

                if (usr != null)
                {
                    res = usr;
                  
                }

            }
            return res;
        }

        public override int MaxInvalidPasswordAttempts
        {
            get { return Membership.Provider.MaxInvalidPasswordAttempts; }
        }

        public override int MinRequiredNonAlphanumericCharacters
        {
            get { return Membership.Provider.MinRequiredNonAlphanumericCharacters; }
        }



        public override int PasswordAttemptWindow
        {
            get { throw new NotImplementedException(); }
        }

        public override MembershipPasswordFormat PasswordFormat
        {
            get { return passwordFormat; }
        }

        public override string PasswordStrengthRegularExpression
        {
            get { throw new NotImplementedException(); }
        }

        public override bool RequiresQuestionAndAnswer
        {
            get { throw new NotImplementedException(); }
        }

        public override bool RequiresUniqueEmail
        {
            get { return _requiresUniqueEmail; }
        }

        public override string ResetPassword(string username, string answer)
        {
            throw new NotImplementedException();
        }

        public override bool UnlockUser(string userName)
        {
            throw new NotImplementedException();
        }

        public override void UpdateUser(MembershipUser user)
        {
            throw new NotImplementedException();
        }

        public override bool ValidateUser(string username, string password)
        {
            throw new NotImplementedException();
        }



        /// <summary>
        /// Encrypts, Hashes, or leaves the password clear based on the <see cref="PasswordFormat"/> property value.
        /// </summary>
        /// <param name="password">the password to encode.</param>
        /// <param name="validationKey">key to use when encoding the password.</param>
        /// <returns>
        /// The encoded password only if all parameters are specified. If <c>validationKey</c> is <c>null</c>
        /// then the given <c>password</c> is returned untouched.
        /// </returns>
        public string EncodePassword(string password, string validationKey, MembershipPasswordFormat pwdFormat)
        {
            // Assume no encoding is performed.
            string encodedPassword = password;

            // Only perform the encoding if all parameters are passed and valid.
            if (!string.IsNullOrEmpty(password) && !string.IsNullOrEmpty(validationKey))
            {
                // Determine the type of encoding required.
                switch (pwdFormat)
                {
                    case MembershipPasswordFormat.Clear:
                        // Nothing to do.
                        break;
                    case MembershipPasswordFormat.Encrypted:
                        encodedPassword = Convert.ToBase64String(EncryptPassword(Encoding.Unicode.GetBytes(password)));
                        break;
                    case MembershipPasswordFormat.Hashed:
                        // If we are not password a validation key, use the default specified.
                        if (string.IsNullOrEmpty(validationKey))
                        {
                            // The machine key will either come from the Web.config file or it will be automatically generate
                            // during initialization.
                            validationKey = machineKey.ValidationKey;
                        }
                        HMACSHA1 hash = new HMACSHA1();
                        hash.Key = HexToByte(validationKey);
                        encodedPassword = Convert.ToBase64String(hash.ComputeHash(Encoding.Unicode.GetBytes(password)));
                        break;
                    default:
                      //  throw ExceptionUtil.NewProviderException(this, Messages.Pwd_UnsupportedFormat);
                        throw new NotImplementedException();
                }
            }

            // Return the encoded password.
            return encodedPassword;
        }
        /// <summary>
        /// Decrypts or leaves the password clear based on the <see cref="PasswordFormat"/> property value.
        /// </summary>
        /// <param name="password">password to unencode.</param>
        /// <returns>Unencoded password.</returns>
        private string UnencodePassword(string password)
        {
            // Assume no unencoding is performed.
            string unencodedPassword = password;

            // Determine the type of unencoding required.
            switch (PasswordFormat)
            {
                case MembershipPasswordFormat.Clear:
                    // Nothing to do.
                    break;
                case MembershipPasswordFormat.Encrypted:
                    unencodedPassword = Encoding.Unicode.GetString(DecryptPassword(Convert.FromBase64String(unencodedPassword)));
                    break;
                case MembershipPasswordFormat.Hashed:
                    //throw ExceptionUtil.NewProviderException(this, Messages.Pwd_CannotUnencodeHashed);
                    throw new NotImplementedException();
                default:
                    //throw ExceptionUtil.NewProviderException(this, Messages.Pwd_UnsupportedFormat);
                    throw new NotImplementedException();
            }

            // Return the unencoded password.
            return unencodedPassword;
        }
        /// <summary>
        /// Converts a hexadecimal string to a byte array. Used to convert encryption key values from the configuration.
        /// </summary>
        /// <param name="hexString">hexadecimal string to conver.</param>
        /// <returns><c>byte</c> array containing the converted hexadecimal string contents.</returns>
        private static byte[] HexToByte(string hexString)
        {
            byte[] bytes = new byte[hexString.Length / 2 + 1];
            for (int i = 0; i <= hexString.Length / 2 - 1; i++)
            {
                bytes[i] = Convert.ToByte(hexString.Substring(i * 2, 2), 16);
            }
            return bytes;
        }




        #region Operations
        /// <summary>
        /// Creates a key based on the indicated size.
        /// </summary>
        /// <param name="numBytes">size of the key.</param>
        /// <returns>Generated key of the specified length.</returns>
        public static string CreateKey(int numBytes)
        {
            RNGCryptoServiceProvider rng = new RNGCryptoServiceProvider();
            byte[] buff = new byte[numBytes];
            rng.GetBytes(buff);
            return BytesToHexString(buff);
        }
        /// <summary>
        /// Converts the given byte array to a hex string representation.
        /// </summary>
        /// <param name="bytes">the data to convert.</param>
        /// <returns>Hexadecimal string representation of the given byte array./</returns>
        public static string BytesToHexString(byte[] bytes)
        {
            StringBuilder hexString = new StringBuilder(64);
            for (int counter = 0; counter < bytes.Length; counter++)
            {
                hexString.Append(string.Format("{0:X2}", bytes[counter]));
            }
            return hexString.ToString();
        }
        #endregion Operations


    }


    /// <summary>
    /// Helper class to generate a machine key for use in the application's config file to override the default key used by the provider.
    /// </summary>
    public static class KeyCreator
    {
        #region Main for Testing
        //public static void main(string[] args)
        //{
        //    string decryptionKey = CreateKey(Convert.ToInt32(args[1]));
        //    string validationKey = CreateKey(Convert.ToInt32(args[2]));
        //    Console.WriteLine("<machinekey validationkey=\"{0}\" decryptionkey=\"{1}\" validation=\"sha1\"/>", validationKey, decryptionKey);
        //}
        #endregion Main for Testing

        #region Operations
        /// <summary>
        /// Creates a key based on the indicated size.
        /// </summary>
        /// <param name="numBytes">size of the key.</param>
        /// <returns>Generated key of the specified length.</returns>
        public static string CreateKey(int numBytes)
        {
            RNGCryptoServiceProvider rng = new RNGCryptoServiceProvider();
            byte[] buff = new byte[numBytes];
            rng.GetBytes(buff);
            return BytesToHexString(buff);
        }
        /// <summary>
        /// Converts the given byte array to a hex string representation.
        /// </summary>
        /// <param name="bytes">the data to convert.</param>
        /// <returns>Hexadecimal string representation of the given byte array./</returns>
        public static string BytesToHexString(byte[] bytes)
        {
            StringBuilder hexString = new StringBuilder(64);
            for (int counter = 0; counter < bytes.Length; counter++)
            {
                hexString.Append(string.Format("{0:X2}", bytes[counter]));
            }
            return hexString.ToString();
        }
        #endregion Operations
    }




}
